//+------------------------------------------------------------------+
//|                                                         Axis.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+

/**
 * @brief 双精度浮点数转字符串的函数指针类型（用于自定义坐标轴数值的格式化逻辑）
 * @param value [in] 待格式化的双精度浮点数（坐标轴上的数值，如价格、时间戳）
 * @param cbdata [in] 自定义回调数据（可传递额外参数，如格式化配置、上下文信息）
 * @return string - 格式化后的字符串（用于坐标轴标签显示）
 * @example 自定义时间戳格式化：将 datetime 类型的数值（秒级时间戳）转换为 "YYYY-MM-DD" 字符串
 */
typedef string(*DoubleToStringFunction)(double value, void* cbdata);

/**
 * @brief 坐标轴类型枚举（定义坐标轴支持的数据类型，决定数值解析与显示逻辑）
 */
enum ENUM_AXIS_TYPE
  {
   AXIS_TYPE_DOUBLE,   // 双精度浮点型坐标轴：用于数值型数据（如价格、成交量、指标值）
   AXIS_TYPE_DATETIME, // 日期时间型坐标轴：用于时间序列数据（如K线时间、日志时间戳）
   AXIS_TYPE_CUSTOM    // 自定义类型坐标轴：通过自定义格式化函数解析数值（如枚举值、编码数据）
  };

//+------------------------------------------------------------------+
//| CAxis 类：二维图形坐标轴核心类                                   |
//| 核心定位：封装二维图形（如K线图、指标图、统计图表）中坐标轴的全部属性与逻辑， |
//|           支持自动缩放、自定义样式、多数据类型适配，为图形渲染提供统一的坐标轴管理 |
//| 核心特性：                                                      |
//|  1. 多类型适配：支持浮点型、日期时间型、自定义型三种坐标轴，覆盖绝大多数数据场景； |
//|  2. 智能自动缩放：根据数据范围自动计算合理的轴范围（最小值/最大值/步长），支持“留白”（Grace）与“零值适配”； |
//|  3. 高度可定制：从外观（颜色、字体、大小）到行为（步长、标签数量、格式化逻辑）均支持精细化配置； |
//|  4. 灵活格式化：支持内置格式字符串与自定义函数两种数值格式化方式，适配复杂显示需求； |
//| 适用场景：所有需要绘制二维图形的场景，典型如：                    |
//|  - 金融图表（K线图的X轴（时间）、Y轴（价格）；指标图的坐标轴）；       |
//|  - 统计分析图表（折线图、柱状图的X轴（分类/时间）、Y轴（数值））；     |
//|  - 自定义监控面板（如服务器性能监控图的CPU使用率、内存占用坐标轴）；   |
//|  - 数据可视化工具（通用图表组件的坐标轴模块，支撑多类型数据展示）；   |
//+------------------------------------------------------------------+
class CAxis
  {
private:
   double            m_min;                  // 坐标轴当前最小值（数据范围的下限）
   double            m_max;                  // 坐标轴当前最大值（数据范围的上限）
   double            m_step;                 // 坐标轴刻度步长（相邻两个刻度之间的数值差）
   uint              m_clr;                  // 坐标轴线条颜色（如轴线、刻度线颜色，默认黑色）
   string            m_name;                 // 坐标轴名称（如“价格”“时间”“成交量”，用于轴标签显示）
   int               m_name_size;            // 坐标轴名称字体大小（默认0，使用系统默认大小）
   int               m_values_size;          // 坐标轴刻度值字体大小（默认12）
   int               m_values_width;         // 坐标轴刻度值显示宽度（单位：像素，默认30，避免标签重叠）
   string            m_values_format;        // 刻度值内置格式化字符串（如 "%.2f" 表示保留2位小数，适配 AXIS_TYPE_DOUBLE）
   string            m_values_fontname;      // 刻度值字体名称（默认 "arial"）
   uint              m_values_fontflags;     // 刻度值字体样式标志（如加粗、斜体，默认0表示常规样式）
   uint              m_values_fontangle;     // 刻度值字体倾斜角度（单位：度，默认0表示水平）
   bool              m_auto_scale;           // 自动缩放开关（true=自动计算轴范围与步长，false=使用手动设置的 min/max/step）
   double            m_zero_lever;           // 零值适配阈值（当最小值占数据范围的比例 < 此值时，轴范围自动包含0，默认0.25=25%）
   double            m_default_step;         // 默认刻度步长（自动缩放时的参考步长，默认25.0）
   double            m_max_labels;           // 最大刻度标签数量（避免标签过多导致重叠，默认15个）
   double            m_min_grace;            // 最小值留白比例（自动缩放时，在数据最小值下方扩展的比例，默认0.01=1%，避免数据贴边）
   double            m_max_grace;            // 最大值留白比例（自动缩放时，在数据最大值上方扩展的比例，默认0.01=1%）
   int               m_values_dt_mode;       // 日期时间显示模式（仅 AXIS_TYPE_DATETIME 有效，默认 TIME_MINUTES，如按分钟/小时/天显示）
   DoubleToStringFunction m_values_func;     // 自定义刻度值格式化函数（AXIS_TYPE_CUSTOM 时生效，替代内置格式化）
   void             *m_values_cbdata;        // 自定义格式化函数的回调数据（传递给 m_values_func 的额外参数）
   ENUM_AXIS_TYPE    m_type;                 // 坐标轴类型（决定数值解析与显示逻辑，默认 AXIS_TYPE_DOUBLE）

public:
   /**
    * @brief 构造函数：创建坐标轴实例并初始化默认属性
    * @note 默认配置：
    *       - 自动缩放开启（m_auto_scale = true）；
    *       - 坐标轴类型为双精度浮点型（m_type = AXIS_TYPE_DOUBLE）；
    *       - 刻度值字体为 Arial 12号常规样式；
    *       - 留白比例 1%，最大标签数15个，零值适配阈值25%；
    */
                     CAxis(void);

   /**
    * @brief 析构函数：清理坐标轴资源
    * @note 无动态内存分配（不持有外部对象引用），仅重置内部状态
    */
                    ~CAxis(void);

   //----------------------------------------------------------------
   // 核心属性访问接口（Getter/Setter）
   //----------------------------------------------------------------
   /** @brief 获取当前刻度步长 */
   double            Step(void) const { return(m_step); }

   /** @brief 获取当前轴范围最小值 */
   double            Min(void) const { return(m_min); }
   /** @brief 设置轴范围最小值（仅当 m_auto_scale = false 时生效） */
   void              Min(const double min) { m_min = min; }

   /** @brief 获取当前轴范围最大值 */
   double            Max(void) const { return(m_max); }
   /** @brief 设置轴范围最大值（仅当 m_auto_scale = false 时生效） */
   void              Max(const double max) { m_max = max; }

   /** @brief 获取坐标轴名称 */
   string            Name(void) const { return(m_name); }
   /** @brief 设置坐标轴名称（用于轴标签显示） */
   void              Name(const string name) { m_name = name; }

   /** @brief 获取坐标轴类型 */
   ENUM_AXIS_TYPE    Type(void) const { return(m_type); }
   /** @brief 设置坐标轴类型（切换类型后需重新调用 SelectAxisScale 生效） */
   void              Type(ENUM_AXIS_TYPE type) { m_type = type; }

   /** @brief 获取坐标轴线条颜色（如轴线、刻度线颜色） */
   uint              Color(void) const { return(m_clr); }
   /** @brief 设置坐标轴线条颜色（支持 MQL5 颜色常量，如 clrRed、clrBlue） */
   void              Color(const uint clr) { m_clr = clr; }

   /** @brief 获取自动缩放开关状态 */
   bool              AutoScale(void) const { return(m_auto_scale); }
   /** @brief 设置自动缩放开关（true=自动计算轴范围与步长，false=手动控制） */
   void              AutoScale(const bool auto_scale) { m_auto_scale = auto_scale; }

   /** @brief 获取刻度值字体大小 */
   int               ValuesSize(void) const { return(m_values_size); }
   /** @brief 设置刻度值字体大小（单位：像素） */
   void              ValuesSize(const int size) { m_values_size = size; }

   /** @brief 获取刻度值显示宽度（用于避免标签重叠） */
   int               ValuesWidth(void) const { return(m_values_width); }
   /** @brief 设置刻度值显示宽度（单位：像素，建议根据字体大小调整） */
   void              ValuesWidth(const int width) { m_values_width = width; }

   /** @brief 获取刻度值内置格式化字符串（仅非自定义类型生效） */
   string            ValuesFormat(void) const { return(m_values_format); }
   /** @brief 设置刻度值内置格式化字符串（如 "%.2f" 表示保留2位小数，"yyyy-MM-dd" 适配日期时间） */
   void              ValuesFormat(const string format) { m_values_format = format; }

   /** @brief 获取日期时间显示模式（仅 AXIS_TYPE_DATETIME 生效） */
   int               ValuesDateTimeMode(void) const { return(m_values_dt_mode); }
   /** @brief 设置日期时间显示模式（支持 TIME_SECONDS/TIME_MINUTES/TIME_HOURS 等常量） */
   void              ValuesDateTimeMode(const int mode) { m_values_dt_mode = mode; }

   /** @brief 获取自定义刻度值格式化函数（仅 AXIS_TYPE_CUSTOM 生效） */
   DoubleToStringFunction ValuesFunctionFormat(void) const { return(m_values_func); }
   /** @brief 设置自定义刻度值格式化函数（替代内置格式化，实现复杂显示逻辑） */
   void              ValuesFunctionFormat(DoubleToStringFunction func) { m_values_func = func; }

   /** @brief 获取自定义格式化函数的回调数据 */
   void*             ValuesFunctionFormatCBData(void) const { return(m_values_cbdata); }
   /** @brief 设置自定义格式化函数的回调数据（传递额外参数给格式化函数） */
   void              ValuesFunctionFormatCBData(void* cbdata) { m_values_cbdata = cbdata; }

   /** @brief 获取刻度值字体名称 */
   string            ValuesFontName(void) const { return(m_values_fontname); }
   /** @brief 设置刻度值字体名称（如 "Arial"、"SimHei" 等系统支持的字体） */
   void              ValuesFontName(const string fontname) { m_values_fontname = fontname; }

   /** @brief 获取刻度值字体倾斜角度 */
   uint              ValuesFontAngle(void) const { return(m_values_fontangle); }
   /** @brief 设置刻度值字体倾斜角度（单位：度，0=水平，90=垂直向上，-90=垂直向下） */
   void              ValuesFontAngle(const uint fontangle) { m_values_fontangle = fontangle; }

   /** @brief 获取刻度值字体样式标志 */
   uint              ValuesFontFlags(void) const { return(m_values_fontflags); }
   /** @brief 设置刻度值字体样式标志（支持 FS_BOLD 加粗、FS_ITALIC 斜体等组合） */
   void              ValuesFontFlags(const uint fontflags) { m_values_fontflags = fontflags; }

   /** @brief 获取坐标轴名称字体大小 */
   int               NameSize(void) const { return(m_name_size); }
   /** @brief 设置坐标轴名称字体大小（单位：像素，0 表示使用系统默认大小） */
   void              NameSize(const int size) { m_name_size = size; }

   /** @brief 获取零值适配阈值（比例） */
   double            ZeroLever(void) const { return(m_zero_lever); }
   /** @brief 设置零值适配阈值（如 0.2 表示当最小值占数据范围 <20% 时，轴范围包含0） */
   void              ZeroLever(const double value) { m_zero_lever = value; }

   /** @brief 获取默认刻度步长（自动缩放时的参考值） */
   double            DefaultStep(void) const { return(m_default_step); }
   /** @brief 设置默认刻度步长（影响自动缩放时的刻度密度） */
   void              DefaultStep(const double value) { m_default_step = value; }

   /** @brief 获取最大刻度标签数量 */
   double            MaxLabels(void) const { return(m_max_labels); }
   /** @brief 设置最大刻度标签数量（避免标签过多重叠，建议 10-20 之间） */
   void              MaxLabels(const double value) { m_max_labels = value; }

   /** @brief 获取最小值留白比例 */
   double            MinGrace(void) const { return(m_min_grace); }
   /** @brief 设置最小值留白比例（如 0.05 表示在数据最小值下方扩展 5% 范围） */
   void              MinGrace(const double value) { m_min_grace = value; }

   /** @brief 获取最大值留白比例 */
   double            MaxGrace(void) const { return(m_max_grace); }
   /** @brief 设置最大值留白比例（如 0.05 表示在数据最大值上方扩展 5% 范围） */
   void              MaxGrace(const double value) { m_max_grace = value; }

   //----------------------------------------------------------------
   // 核心功能接口
   //----------------------------------------------------------------
   /**
    * @brief 坐标轴缩放核心方法：计算并更新轴的最小值、最大值与刻度步长
    * @note 执行逻辑：
    *       1. 若 m_auto_scale = true：
    *          - 调用 ExtensionBoundaries 扩展轴范围（添加留白、处理零值适配）；
    *          - 处理数据范围为0的特殊情况（如所有数据相同，自动扩展合理范围）；
    *          - 调用 CalcStepSize/CalcBoundedStepSize 计算最优刻度步长（适配最大标签数量）；
    *          - 对齐最小值与最大值（确保刻度与轴范围边界对齐）；
    *       2. 若 m_auto_scale = false：
    *          - 检查手动设置的 min/max 是否合理（若 max ≤ min，自动扩展范围）；
    *          - 使用手动设置的 step 或默认 step；
    *       3. 调用时机：数据更新后、坐标轴类型切换后、关键属性（如 AutoScale、MinGrace）修改后需调用此方法生效；
    */
   void              SelectAxisScale(void);

private:
   //----------------------------------------------------------------
   // 内部辅助方法（不对外暴露，支撑核心功能实现）
   //----------------------------------------------------------------
   /**
    * @brief 扩展轴范围：根据留白比例（MinGrace/MaxGrace）和零值适配阈值（ZeroLever）调整 min/max
    * @note 功能：避免数据紧贴坐标轴边界，提升图表可读性；处理所有数据为正/负时是否包含零的逻辑；
    */
   void              ExtensionBoundaries(void);

   /**
    * @brief 计算刻度步长：根据数据范围和目标步长数量，生成“友好”的步长值（如 1、2、5、10 等，避免 3.7 这类不规则值）
    * @param range [in] 数据范围（max - min）
    * @param steps [in] 目标步长数量（参考值，如默认 step 对应的数量）
    * @return double - 计算出的最优步长值
    */
   double            CalcStepSize(const double range, const double steps);

   /**
    * @brief 安全取模运算：计算 x 除以 y 的余数，避免 y=0 导致的错误
    * @param x [in] 被除数（如坐标轴数值）
    * @param y [in] 除数（如刻度步长）
    * @return double - 余数（x 为负数时返回非负值，确保轴范围对齐逻辑正确）
    */
   double            Mod(const double x, const double y);

   /**
    * @brief 计算有界刻度步长：在 CalcStepSize 基础上，确保步长对应的刻度数量不超过最大标签数（MaxLabels）
    * @param range [in] 数据范围（max - min）
    * @param max_steps [in] 最大允许的步长数量（即 MaxLabels）
    * @return double - 适配最大标签数的最优步长值
    */
   double            CalcBoundedStepSize(const double range, const double max_steps);
  };
//+------------------------------------------------------------------+